package jamirr;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public interface TypeInformation<T> extends TypeVariableResolver, Type {

    /**
     * @return 类型
     */
    Class<T> getType();

    @Override
    default String getTypeName() {
        Argument<?>[] typeParameters = getTypeParameters();
        if (typeParameters != null && typeParameters.length > 0) {
            String typeName = getType().getTypeName();
            return typeName + "<" + Arrays.stream(typeParameters)
                    .map(Argument::getTypeName)
                    .collect(Collectors.joining(",")) + ">";
        } else {
            return getType().getTypeName();
        }
    }

    /**
     * @return Is the type primitive.
     */
    default boolean isPrimitive() {
        return getType().isPrimitive();
    }

    /**
     * @return Is the return type {@link Optional}.
     */
    default boolean isOptional() {
        Class<T> type = getType();
        return type == Optional.class;
    }

    /**
     * @return Is the type an array.
     */
    default boolean isArray() {
        return getType().isArray();
    }

    /**
     * @return Whether this is a container type.
     */
    default boolean isCollection() {
        final Class<T> type = getType();
        return Map.class == type
                || ArgumentImpl.CONTAINER_TYPES.contains(type.getName());
    }

    /**
     * Obtains the type's simple name.
     *
     * @return The simple name
     */
    default String getSimpleName() {
        return getType().getSimpleName();
    }

    /**
     * If the type is primitive returns the wrapper type, otherwise returns the actual type.
     *
     * @return The wrapper type if primitive
     */
    default Class<?> getWrapperType() {
        if (isPrimitive()) {
            return Utils.getWrapperType(getType());
        } else {
            return getType();
        }
    }

    /**
     * Represent this argument as a {@link Type}.
     *
     * @return The {@link Type}
     */
    default Type asType() {
        if (getTypeParameters().length == 0) {
            return getType();
        }
        return asParameterizedType();
    }

    /**
     * Represent this argument as a {@link ParameterizedType}.
     *
     * @return The {@link ParameterizedType}
     */
    default ParameterizedType asParameterizedType() {
        return new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return Arrays.stream(getTypeParameters())
                        .map(TypeInformation::asType)
                        .toArray(Type[]::new);
            }

            @Override
            public Type getRawType() {
                return TypeInformation.this.getType();
            }

            @Override
            public Type getOwnerType() {
                return null;
            }

            @Override
            public String getTypeName() {
                return TypeInformation.this.getTypeName();
            }

            @Override
            public String toString() {
                return getTypeName();
            }

            @Override
            public int hashCode() {
                return Arrays.hashCode(getActualTypeArguments()) ^ Objects.hashCode(getOwnerType()) ^ Objects.hashCode(getRawType());
            }

            @Override
            public boolean equals(Object o) {
                if (o instanceof ParameterizedType that) {
                    if (this == that) {
                        return true;
                    }
                    return Objects.equals(getOwnerType(), that.getOwnerType())
                            && Objects.equals(getRawType(), that.getRawType()) &&
                            Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments());
                }
                return false;
            }
        };
    }

}
